﻿using System;
using System.Collections.Generic;
using System.Data;
using Dapper;
using System.Linq;
using System.Data.Common;
using System.Threading.Tasks;

namespace Dapper.Moon
{
    /// <summary>
    /// 默认数据库通用操作,由Dapper实现
    /// </summary>
    public abstract class BaseDatabase
    {
        protected IDbConnection _Connection { get; set; }
        protected IDbTransaction _Transaction { get; set; }
        protected int _CommandTimeout = 3600;
        protected IAop _Aop { get; set; }

        public BaseDatabase()
        {
        }

        protected virtual ExecutingArgs OnExecuting(string sql, object param, ExecuteType executeType)
        {
            if (_Aop != null && _Aop.OnExecuting != null)
            {
                ExecutingArgs executingArgs = new ExecutingArgs(sql,
                    (param != null && param is DynamicParameters) ? param as DynamicParameters : null)
                {
                    LogId = Guid.NewGuid().ToString("N"),
                    ExecuteType = executeType
                };
                _Aop.OnExecuting(executingArgs);
                return executingArgs;
            }
            return null;
        }

        protected virtual ExecutedArgs OnExecuted(ExecutingArgs args, Exception exception, object executeResult)
        {
            if (_Aop != null && _Aop.OnExecuted != null)
            {
                var executedArgs = new ExecutedArgs(args, exception, executeResult)
                {
                    LogId = args.LogId,
                    ExecuteType = args.ExecuteType
                };
                _Aop.OnExecuted(executedArgs);
                return executedArgs;
            }
            return null;
        }

        public virtual List<T> Query<T>(string sql, object param = null, CommandType? commandType = null)
        {
            List<T> list = null;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                list = _Connection.Query<T>(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType).ToList();
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, list);
            }
            return list;
        }

        public virtual async Task<List<T>> QueryAsync<T>(string sql, object param = null, CommandType? commandType = null)
        {
            List<T> list = null;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                var result = await _Connection.QueryAsync<T>(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
                list = result.ToList();
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, list);
            }
            return list;
        }

        public virtual T QueryFirst<T>(string sql, object param = null, CommandType? commandType = null)
        {
            T result = default(T);
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                result = _Connection.QueryFirstOrDefault<T>(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, result);
            }
            return result;
        }

        public virtual async Task<T> QueryFirstAsync<T>(string sql, object param = null, CommandType? commandType = null)
        {
            T result = default(T);
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                result = await _Connection.QueryFirstOrDefaultAsync<T>(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, result);
            }
            return result;
        }

        public virtual QueryPageResult<T> QueryPage<T>(string sql, object param = null)
        {
            QueryPageResult<T> result = null;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                using (var query = QueryMultiple(sql, param))
                {
                    int total = query.ReadFirstOrDefault<int>();
                    var rows = query.Read<T>().ToList();
                    result = new QueryPageResult<T>() { rows = rows, total = total };
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, result);
            }
            return result;
        }

        public virtual async Task<QueryPageResult<T>> QueryPageAsync<T>(string sql, object param = null)
        {
            QueryPageResult<T> pageResult = null;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                using (var query = await QueryMultipleAsync(sql, param))
                {
                    int total = await query.ReadFirstOrDefaultAsync<int>();
                    var result = await query.ReadAsync<T>();
                    var rows = result.ToList();
                    pageResult = new QueryPageResult<T>() { rows = rows, total = total };
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, pageResult);
            }
            return pageResult;
        }

        public virtual Dapper.SqlMapper.GridReader QueryMultiple(string sql, object param = null, CommandType? commandType = null)
        {
            Dapper.SqlMapper.GridReader gridReader = null;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                gridReader = _Connection.QueryMultiple(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, gridReader);
            }
            return gridReader;
        }

        public virtual async Task<Dapper.SqlMapper.GridReader> QueryMultipleAsync(string sql, object param = null, CommandType? commandType = null)
        {
            Dapper.SqlMapper.GridReader gridReader = null;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                gridReader = await _Connection.QueryMultipleAsync(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, gridReader);
            }
            return gridReader;
        }

        public virtual DataTable Query(string sql, object param = null, CommandType? commandType = null)
        {
            DataTable table = new DataTable();
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                using (var reader = _Connection.ExecuteReader(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType))
                {
                    table.Load(reader);
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, table);
            }
            return table;
        }

        public virtual async Task<DataTable> QueryAsync(string sql, object param = null, CommandType? commandType = null)
        {
            DataTable table = new DataTable();
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                using (var reader = await _Connection.ExecuteReaderAsync(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType))
                {
                    table.Load(reader);
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, table);
            }
            return table;
        }

        public virtual int Execute(string sql, object param = null, CommandType? commandType = null)
        {
            int rowcount = 0;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.NonQuery);
            try
            {
                rowcount = _Connection.Execute(sql, param, commandTimeout: _CommandTimeout, transaction: _Transaction, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, rowcount);
            }
            return rowcount;
        }

        public virtual async Task<int> ExecuteAsync(string sql, object param = null, CommandType? commandType = null)
        {
            int rowcount = 0;
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.NonQuery);
            try
            {
                rowcount = await _Connection.ExecuteAsync(sql, param, commandTimeout: _CommandTimeout, transaction: _Transaction, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, rowcount);
            }
            return rowcount;
        }

        public virtual T ExecuteScalar<T>(string sql, object param = null, CommandType? commandType = null)
        {
            T result = default(T);
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                result = _Connection.ExecuteScalar<T>(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, result);
            }
            return result;
        }

        public virtual async Task<T> ExecuteScalarAsync<T>(string sql, object param = null, CommandType? commandType = null)
        {
            T result = default(T);
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            try
            {
                result = await _Connection.ExecuteScalarAsync<T>(sql, param, transaction: _Transaction, commandTimeout: _CommandTimeout, commandType: commandType);
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                OnExecuted(executingArgs, exception, result);
            }
            return result;
        }

        public DataSet ExecuteDataSet(string sql, SqlMapper.IDynamicParameters param = null, CommandType? commandType = null)
        {
            DataSet ds = new DataSet();
            Exception exception = null;
            var executingArgs = OnExecuting(sql, param, ExecuteType.Query);
            bool wasClosed = _Connection.State == ConnectionState.Closed;
            try
            {
                if (wasClosed) _Connection.Open();
                using (IDbCommand cmd = GetCommand(sql, param, commandType))
                {
                    if (_Transaction != null)
                    {
                        cmd.Transaction = _Transaction;
                    }
                    IDataAdapter adapter = GetAdapter(cmd);
                    adapter.Fill(ds);
                    cmd.Parameters?.Clear();
                }
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
            finally
            {
                if (wasClosed) _Connection.Close();
                OnExecuted(executingArgs, exception, ds);
            }
            return ds;
        }

        protected abstract IDbCommand GetCommand(string sql, SqlMapper.IDynamicParameters param = null, CommandType? commandType = null);
        protected abstract IDataAdapter GetAdapter(IDbCommand command);
        public abstract int BulkInsert(DataTable table, string tableName);
        public abstract Task<int> BulkInsertAsync(DataTable table, string tableName);

        #region 事务
        public void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            if (_Connection.State == ConnectionState.Closed) _Connection.Open();
            if (_Transaction == null)
            {
                _Transaction = _Connection.BeginTransaction(isolationLevel);
            }
        }

        public void CommitTransaction()
        {
            _Transaction?.Commit();
            _Transaction = null;
            if (_Connection.State == ConnectionState.Open) _Connection.Close();
        }

        public void RollbackTransaction()
        {
            _Transaction?.Rollback();
            _Transaction = null;
            if (_Connection.State == ConnectionState.Open) _Connection.Close();
        }

        public RunTransactionResult RunTransaction(Action action, IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
        {
            RunTransactionResult runTransactionResult = new RunTransactionResult()
            {
                Success = true
            };
            bool wasClosed = _Connection.State == ConnectionState.Closed;
            try
            {
                if (wasClosed) _Connection.Open();
                BeginTransaction(isolationLevel);
                action();
                CommitTransaction();
            }
            catch (Exception ex)
            {
                runTransactionResult.Success = false;
                runTransactionResult.Exception = ex;
                RollbackTransaction();
            }
            finally
            {
                if (wasClosed) _Connection.Close();
            }
            return runTransactionResult;
        }
        #endregion 事务
    }
}
